Snowbridge: Linear-parameterized receipt-log benchmarks#12177
Conversation
| } | ||
| } | ||
|
|
||
| /// Build a real receipts trie with `n` leaves, each holding the same `s`-byte receipt |
There was a problem hiding this comment.
n is documented as nodes everywhere but here it is documented as leaves. Are we just using leaves to build up the the receipt proof with mock data (no nodes).
| // `MaxMessageSize` (the upper bound on the inbound message payload). | ||
| let max_message_size = T::MaxMessageSize::get(); | ||
| let weight_fee = T::WeightToFee::weight_to_fee(&T::WeightInfo::submit( | ||
| max_message_size, |
There was a problem hiding this comment.
We are parsing in max_message_size as the proof node count. Wont this bloat the delivery cost?
There was a problem hiding this comment.
Yeah, seems like it could result in a 25x increase in cost. We need to check if this won't result in unexpected problems.
| - audience: Runtime Dev | ||
| description: |- | ||
| Replaces the constant-time weights for snowbridge's three Ethereum-event-consuming extrinsics with `Linear<MIN, MAX>` benchmarks parameterized by the actual cost drivers: receipt-proof node count `n` and receipt envelope size `s`. Affects `pallet-inbound-queue::submit`, `pallet-inbound-queue-v2::submit`, and `pallet-outbound-queue-v2::submit_delivery_receipt`. | ||
| Each pallet's `BenchmarkHelper::initialize_storage` now takes `(n, s)`, the corresponding `WeightInfo` method takes `(u32, u32)`, and the dispatch reads `n` / `s` from `event.proof.receipt_proof` so callers are charged in proportion to verifier work rather than worst-case. Adds runtime-benchmarks-only Config items `MaxProofNodes` and `MaxReceiptBytes` as benchmark-fit upper bounds (they do NOT bound proof or receipt sizes at runtime; the verifier already enforces gas-style cost limits). |
There was a problem hiding this comment.
the verifier already enforces gas-style cost limits
What do we mean by this?
| /// proof. Does NOT bound proof size at runtime — the inbound message verifier rejects | ||
| /// proofs that exceed plausible sizes through its own gas-style cost accounting. | ||
| #[cfg(feature = "runtime-benchmarks")] | ||
| type MaxProofNodes: Get<u32>; |
There was a problem hiding this comment.
Wondering if we shouldn't bound the proof nodes, in general (not just for runtime benchmarks)?
| // `MaxMessageSize` (the upper bound on the inbound message payload). | ||
| let max_message_size = T::MaxMessageSize::get(); | ||
| let weight_fee = T::WeightToFee::weight_to_fee(&T::WeightInfo::submit( | ||
| max_message_size, |
There was a problem hiding this comment.
Yeah, seems like it could result in a 25x increase in cost. We need to check if this won't result in unexpected problems.
| Weight::from_parts(225_000_000, 0) | ||
| .saturating_add(Weight::from_parts(0, 3775)) | ||
| // Per proof-node cost: ~3 ms / node from receipt-trie traversal. | ||
| .saturating_add(Weight::from_parts(3_000_000, 0).saturating_mul(n.into())) | ||
| // Per receipt-byte cost: ~2 us / byte from RLP decode and log scan. | ||
| .saturating_add(Weight::from_parts(2_000, 0).saturating_mul(s.into())) | ||
| .saturating_add(T::DbWeight::get().reads(9)) |
There was a problem hiding this comment.
Have these and the Outbound V2 benchmarks been rerun? I noticed they look kinda of similar, checking that these have been run and not hardcoded.
| // The receipt is stored as the value of the leaf (last) node of the receipt-trie | ||
| // proof, so the leaf's encoded length is a tight upper bound for the receipt size | ||
| // at dispatch time, before verification has run. | ||
| event.proof.receipt_proof.last().map(|leaf| leaf.len() as u32).unwrap_or(0), |
There was a problem hiding this comment.
| event.proof.receipt_proof.last().map(|leaf| leaf.len() as u32).unwrap_or(0), | |
| event | |
| .proof | |
| .receipt_proof | |
| .iter() | |
| .fold(0u32, |acc, node| acc.saturating_add(node.len() as u32)), |
This is more accurate than just using the leaf, by charging per-byte weight against the total size of all receipt-proof nodes. Because the caller can inflate trie nodes that verifier still has to hash without paying for that work.
Summary
Replaces the constant-time submit / submit_delivery_receipt weights for snowbridge's three Ethereum-event-consuming extrinsics (pallet-inbound-queue::submit,
pallet-inbound-queue-v2::submit, pallet-outbound-queue-v2::submit_delivery_receipt) with Linear<MIN, MAX> benchmarks parameterized by the actual cost drivers: receipt-proof
node count n and receipt size s. Charges callers in proportion to the work the verifier does on their input rather than worst-case.
Why
Today these extrinsics declare a single constant weight, which has two failure modes:
Both costs are dominated by:
So a 2-D linear fit over (n, s) is the right shape.
How
Introduces a shared dynamic-fixture builder in
snowbridge-pallet-ethereum-client-fixtures::dynamic(the inverse ofpallet-ethereum-client::Pallet::verify) that synthesizesEventFixtures mirroring the verifier's SSZ + merkle + receipts-trie logic.